﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using org.mayb.Fixtures;

namespace org.mayb.Fixtures
{
    public class DatabaseSerializer
    {
        private DataContext _dc;
        private Stream _stream;
        private StreamWriter _writer;
        private bool _separateStreamMode;
        private string _directory = "";
        public DatabaseSerializer(DataContext dc)
        {
            this._dc = dc;
            this._stream = new MemoryStream(100000);
            this._writer = new StreamWriter(this._stream);
        }

        private class TableInfo
        {
            public bool HasPK { get; set; }
            public List<string> SkipColumns { get; set; } 
        }

        public void SerializeToDirectory(string directory)
        {
            if (!Directory.Exists(directory))
                Directory.CreateDirectory(directory);

            _separateStreamMode = true;
            this._directory = directory;

            SerializeDatabase();

            this._writer.Flush();
            this._stream.Flush();
            this._writer.Close();
            this._stream.Close();
        }

        public void SerializeToStream(Stream stream)
        {
            SerializeDatabase();
            this._writer.Flush();

            this._stream.Seek(0, SeekOrigin.Begin);

            CopyStream(this._stream, stream);
        }

        private static void CopyStream(Stream input, Stream output)
        {
            byte[] buffer = new byte[32768];
            int read;
            while((read = input.Read(buffer, 0, buffer.Length)) > 0)
            {
                output.Write(buffer, 0, read);
            }
            output.Flush();
        }

        private TableInfo GetTableInfo(Type instance)
        {
            TableInfo ti = new TableInfo();
            ti.HasPK = false;
            ti.SkipColumns = new List<string>();

            foreach (PropertyInfo prop in instance.GetProperties())
            {
                AssociationAttribute[] aas = (AssociationAttribute[])prop.GetCustomAttributes(typeof(AssociationAttribute), false);
                foreach(AssociationAttribute aa in aas)
                {
                    if(aa.IsForeignKey)
                    {
                        ti.SkipColumns.Add(aa.ThisKey);
                    }
                }

                ColumnAttribute[] cas = (ColumnAttribute[])prop.GetCustomAttributes(typeof (ColumnAttribute), false);
                foreach(ColumnAttribute ca in cas)
                {
                    if(ca.IsPrimaryKey)
                        ti.HasPK = true;
                    if(ca.IsDbGenerated)
                        ti.SkipColumns.Add(prop.Name);
                }
            }

            return ti;
        }

        private void SerializeDatabase()
        {
            foreach(PropertyInfo pi in _dc.GetType().GetProperties())
            {
                Type propType = pi.PropertyType;
                Type[] interfaces = propType.GetInterfaces();
                if(interfaces.Contains(typeof(ITable)))
                {
                    this.PrepareForInstances(propType.GetGenericArguments()[0].Name);
                    SerializeTable((ITable)pi.GetValue(this._dc, null));

                }
            }
        }

        private void PrepareForInstances(string name)
        {
            if(_separateStreamMode)
            {
                this._writer.Flush();
                this._stream.Flush();
                this._writer.Close();
                this._stream.Close();

                this._stream = new FileStream(this._directory + "\\" + name + ".fix", FileMode.Create, FileAccess.ReadWrite);
                this._writer = new StreamWriter(this._stream);
            }
            else
            {
                this._writer.WriteLine("\r\n/* Serializing Instances of {0} */", name);
            }
        }

        private void SerializeTable(ITable table)
        {
            Type tableType = table.GetType();
            Type instanceType = tableType.GetGenericArguments()[0];

            TableInfo ti = GetTableInfo(instanceType);

            if (ti.HasPK)
            {
                foreach (object instance in table)
                {
                    SerialzeInstance(instance, ti);
                    _writer.WriteLine("");
                }
            }
            else
            {
                System.Console.WriteLine("Warning: Table for {0} does not have a primary key. Unable to create fixtures.", instanceType.Name);
                _writer.WriteLine("/* Warning: Table for {0} does not have a primary key. Unable to create fixtures. */", instanceType.Name);
            }
        }

        private void SerialzeInstance(object instance, TableInfo ti)
        {
            Type type = instance.GetType();
            _writer.WriteLine("{0} = {1} {{", GenerateID(instance), type.Name);

            bool first = true;

            foreach(PropertyInfo pi in type.GetProperties())
            {
                if(ti.SkipColumns.Contains(pi.Name))
                    continue;

                if(pi.CanRead)
                {
                    ColumnAttribute[] columnAttrs = (ColumnAttribute[])pi.GetCustomAttributes(typeof (ColumnAttribute), false);
                    if(columnAttrs.Length > 0)
                    {
                        SerializeColumn(instance, pi, first);
                        first = false;
                    }

                    AssociationAttribute[] associationAttributes = (AssociationAttribute[])pi.GetCustomAttributes(typeof (AssociationAttribute), false);
                    if(associationAttributes.Length > 0)
                    {
                        if(associationAttributes[0].IsForeignKey)
                        {
                            SerialzeForeignKeyAssociation(instance, pi, first);
                            first = false;
                        }
                    }


                }
            }

            _writer.WriteLine("\r\n}");
        }

        Regex rex = new Regex("[^0-9a-zA-Z]");

        private string PrepareValue(string value)
        {
            if (value == "" || value.Contains(" ") || rex.IsMatch(value))
            {
                value = value.Replace("{", "\\{");
                value = value.Replace("}", "\\}");
                value = "@{" + value + "}";
            }

            return value;
        }

        private void SerializeColumn(object instance, PropertyInfo pi, bool first)
        {
            object columnValue = pi.GetValue(instance, null);
            if (columnValue != null)
            {
                string value = columnValue.ToString();

                value = PrepareValue(value);

                if (!first)
                {
                    _writer.Write(",\r\n");
                }

                _writer.Write("\t{0}: {1}", pi.Name, value);
            }
        }

        private void SerialzeForeignKeyAssociation(object instance, PropertyInfo pi, bool first)
        {
            object relation = pi.GetValue(instance, null);
            if(relation != null)
            {
                if (!first)
                {
                    _writer.Write(",\r\n");
                }
                _writer.Write("\t{0}: ${1}", pi.Name, GenerateID(relation));
            }
        }

        private void SerialzeAssociation(object instance, PropertyInfo pi, bool first)
        {
            string value = "[";
            object rels = pi.GetValue(instance, null);
            foreach(object relation in (IEnumerable)rels)
            {
                if (value != "[") value += ", ";

                value += "$" + GenerateID(relation);
            }
            value += "]";

            if (!first)
            {
                _writer.Write(",\r\n");
            }

            _writer.Write("\t{0}: {1}", pi.Name, value);
        }

        private string GenerateID(object instance)
        {
            Type type = instance.GetType();
            string id = type.Name;
            foreach(PropertyInfo pi in type.GetProperties())
            {
                ColumnAttribute[] attrs = (ColumnAttribute[])pi.GetCustomAttributes(typeof (ColumnAttribute), false);
                if(attrs.Length > 0 && attrs[0].IsPrimaryKey)
                {
                    id += "_" + pi.GetValue(instance, null);
                }
            }

            return id;
        }

        

    }


    
}

public static class DataContextExtension
{
    public static void SerializeToFixtures(this DataContext dc, string directory)
    {
        DatabaseSerializer ds = new DatabaseSerializer(dc);
        ds.SerializeToDirectory(directory);
    }
}
